﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
using System.Xml;
using System.Text;
using System.Net;
using System.Timers;
using System.IO;
using System.Net.Sockets;

namespace LogSystem
{

    public enum LogLevel
    {
        Info,
        Warning,
        Error,
    }

    public static class LogSys
    {

        private struct LogPacket
        {
            public LogLevel logLevel;
            public string log;
        }

        //private static readonly Mutex mux = new Mutex();
        private static List<string> logList = new List<string>();
        private static List<string> tmpList = new List<string>();

        private static System.Timers.Timer logTimer;
        private static System.Timers.Timer autoSaveTimer;


        private static bool isInit = false;

        private static FileStream writeFS;

        private static Socket udpSocket;

        #region 读取外部配置
        
        private static Encoding encoding;

        private static string savePath;
        private static string fullSavePath;
        private static int autoSaveInterval;

        private static bool isWriteFile = false;

        private static string logPrefix;

        private static bool isUseUDP = false;
        private static EndPoint endPoint;
        #endregion

        /// <summary>
        /// 指定文件夹路径
        /// </summary>
        /// <param name="path"></param>
        public static void CreateConfigrationXML(string xmlPath)
        {
         
            XmlDocument xmlDoc = new XmlDocument();
            //设置xml的版本号和字符编码等
            XmlDeclaration dec = xmlDoc.CreateXmlDeclaration("1.0", "utf-8", null);
            xmlDoc.AppendChild(dec);
            XmlComment commentTip1 = xmlDoc.CreateComment("提示:节点中的值前后去掉无效的空白，否则会出错");
            xmlDoc.AppendChild(commentTip1);
            XmlComment commentTip2 = xmlDoc.CreateComment("<port>5555</port>与<port>5555 </port>不同");
            xmlDoc.AppendChild(commentTip2);
            //创建根节点
            XmlNode root = xmlDoc.CreateElement("Configration");
            xmlDoc.AppendChild(root);


            XmlComment comment5 = xmlDoc.CreateComment("字符编码方式 ASCII UTF-8 Unicode");
            root.AppendChild(comment5);
            XmlElement encoding = xmlDoc.CreateElement("Encoding");
            encoding.InnerText = "ASCII";
            root.AppendChild(encoding);



            XmlElement fileWrite = xmlDoc.CreateElement("FileWrite");
            XmlAttribute enableWrite = xmlDoc.CreateAttribute("Enable");
            enableWrite.InnerText = "true";
            fileWrite.SetAttributeNode(enableWrite);
            //文件保存路径
            XmlComment comment1 = xmlDoc.CreateComment("文件保存路径");
            fileWrite.AppendChild(comment1);
            XmlElement savePath = xmlDoc.CreateElement("SaveFilePath");

            savePath.InnerText = "./";
            fileWrite.AppendChild(savePath);

            XmlComment comment4 = xmlDoc.CreateComment("自动保存时间 单位秒 最小值60（正整数）");
            fileWrite.AppendChild(comment4);
            XmlElement autoSaveInterval = xmlDoc.CreateElement("AutoSaveInterval");
            autoSaveInterval.InnerText = "10";
            fileWrite.AppendChild(autoSaveInterval);

            XmlComment comment6 = xmlDoc.CreateComment("日志前缀 不能有空格");
            fileWrite.AppendChild(comment6);
            XmlElement prefix = xmlDoc.CreateElement("LogPrefix");
            prefix.InnerText = "Demo";
            fileWrite.AppendChild(prefix);
            root.AppendChild(fileWrite);


            //UDP发送目标主机
            XmlComment comment3 = xmlDoc.CreateComment("UDP发送目标主机");
            root.AppendChild(comment3);
            XmlElement endPoint = xmlDoc.CreateElement("EndPoint");
            XmlAttribute enableUDP = xmlDoc.CreateAttribute("Enable");
            enableUDP.InnerText = "true";
            endPoint.SetAttributeNode(enableUDP);
            XmlElement ipAddress = xmlDoc.CreateElement("IPAddress");
            ipAddress.InnerText = "127.0.0.1";
            endPoint.AppendChild(ipAddress);
            XmlElement port = xmlDoc.CreateElement("Port");
            port.InnerText = "5555";
            endPoint.AppendChild(port);
            root.AppendChild(endPoint);

            //保存
            xmlDoc.Save(xmlPath);
        }

        /// <summary>
        /// 初始化配置
        /// </summary>
        /// <param name="xmlPath"></param>
        public static void InitializeLogSystem(string xmlPath)
        {
            if(!File.Exists(xmlPath))
            {
                throw new Exception("配置文件不存在");
            }
            if (isInit) return;
            isInit = true;

            XmlDocument doc = new XmlDocument();
            XmlReaderSettings setting = new XmlReaderSettings();
            setting.IgnoreComments = true;
            setting.IgnoreWhitespace = true;
            XmlReader reader = XmlReader.Create(xmlPath, setting);
            doc.Load(reader);
            //XmlNode fnode = doc.FirstChild;
            //Console.WriteLine(fnode.Value);
            //if (fnode.NodeType == XmlNodeType.XmlDeclaration)
            //{
            //    Console.WriteLine(((XmlDeclaration)fnode).Version);
            //    Console.WriteLine(((XmlDeclaration)fnode).Encoding);
            //}
            XmlNode rootNode = doc.SelectSingleNode("Configration");

            string encodingStr = rootNode.SelectSingleNode("Encoding").InnerText;
            if(string.Compare(encodingStr, "UTF-8", true) == 0)
                encoding = new UTF8Encoding();
            else if(string.Compare(encodingStr, "Unicode", true) == 0)
                encoding = new UnicodeEncoding();
            else
                encoding = new ASCIIEncoding();

            XmlNode fileWriteNode = rootNode.SelectSingleNode("FileWrite");
            if (fileWriteNode.Attributes[0].Name == "Enable" && string.Compare(fileWriteNode.Attributes[0].Value, "true", true) == 0 )
            {
                isWriteFile = true;
                savePath = fileWriteNode.SelectSingleNode("SaveFilePath").InnerText;
                string s_autoSaveTime = fileWriteNode.SelectSingleNode("AutoSaveInterval").InnerText;
                if(!int.TryParse(s_autoSaveTime, out autoSaveInterval))
                {
                    throw new Exception("AutoSaveInterval Node Error");
                }
                if (autoSaveInterval <= 0) autoSaveInterval = 10;

                logPrefix = fileWriteNode.SelectSingleNode("LogPrefix").InnerText;

                logTimer = new System.Timers.Timer(1000);
                logTimer.Elapsed += new ElapsedEventHandler(LogTimerEvent);
                logTimer.AutoReset = true;
                logTimer.Enabled = true;

                autoSaveTimer = new System.Timers.Timer(autoSaveInterval * 1000);
                autoSaveTimer.Elapsed += new ElapsedEventHandler(AutoSaveEvent);
                autoSaveTimer.AutoReset = true;
                autoSaveTimer.Enabled = true;
                if (!savePath.EndsWith("/")) savePath += '/';
                savePath += logPrefix + " " + DateTime.Now.ToString("yyyy-MM-dd HH_mm_ss")+" log";
                fullSavePath = savePath + ".txt";
      
                if(File.Exists(savePath + ".txt"))
                {
                    writeFS = new FileStream(fullSavePath, FileMode.Truncate);
                }
                else
                {
                    writeFS = new FileStream(fullSavePath, FileMode.Create);
                }

                logList.Add("========== Start debug " + logPrefix + " program ==========\n");
            }
            else
            {
                isWriteFile = false;
            }

            XmlNode udpNode = rootNode.SelectSingleNode("EndPoint");
            if(udpNode.Attributes[0].Name == "Enable" && string.Compare(udpNode.Attributes[0].Value, "true", true) == 0)
            {
                isUseUDP = true;
                string ip = udpNode.SelectSingleNode("IPAddress").InnerText;
                IPAddress ipaddr;
                if (!IPAddress.TryParse(ip, out ipaddr))
                {
                    throw new Exception("IPAddress Node Error");
                }
                string s_port = udpNode.SelectSingleNode("Port").InnerText;
                int port;
                if(!int.TryParse(s_port, out port))
                {
                    throw new Exception("Port Node Error");
                }
                endPoint = new IPEndPoint(ipaddr, port);

                udpSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);

                udpSocket.SendTo(encoding.GetBytes("========== Start debug " + logPrefix + " program =========="), endPoint);
            }
            else
            {
                isUseUDP = false;
            }
           
        }

        private static void AutoSaveEvent(object sender, ElapsedEventArgs e)
        {
            lock (writeFS)
            {
                writeFS.Close();
                File.Copy(fullSavePath, savePath + "_bk.txt", true);
                writeFS = new FileStream(fullSavePath, FileMode.Append);
            }
        }

        private static void LogTimerEvent(object sender, ElapsedEventArgs e)
        {
            lock(logList)
            {
                lock(tmpList)
                {
                    for (int i = 0; i < tmpList.Count; i++)
                    {
                        logList.Add(tmpList[i]);
                    }
                    tmpList.Clear();
                }
            }
            ///写入文件
            lock(logList)
            {
                lock(writeFS)
                {
                    int logsize = logList.Count;
                    for (int i = 0; i < logsize; i++)
                    {
                        byte[] buff = encoding.GetBytes(logList[i]);
                        writeFS.Write(buff, 0, buff.Length);
                    }
                    logList.Clear();
                }
                
            }
        }

        public static void DeInitializeLogSystem()
        {
            if(logTimer != null)
            {
                logTimer.Dispose();
                logTimer.Close();
            }
            if(autoSaveTimer != null)
            {
                autoSaveTimer.Dispose();
                autoSaveTimer.Close();
            }

            if(isWriteFile)
            {
                //关闭前写入日志
                logList.Add("========== End debug " + logPrefix + " program ==========\n");
                LogTimerEvent(null, null);
                //AutoSaveEvent(null, null);
                lock(writeFS)
                {
                    writeFS.Close();
                }
            }

            if(isUseUDP)
            {
                udpSocket.SendTo(encoding.GetBytes("========== End debug " + logPrefix + " program =========="), endPoint);
            }
        }

        /// <summary>
        /// 打印日志
        /// </summary>
        /// <param name="logLevel"></param>
        /// <param name="log"></param>
        public static void Log(LogLevel logLevel, string log)
        {
            if(isWriteFile)
            {
                string writeStr = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff") + " " + logLevel.ToString() + ":" + log;

                lock (tmpList)
                {
                    tmpList.Add(writeStr + '\n');
                }
            }

            string logStr = logLevel.ToString() + ":" + log;
            if (isUseUDP)
            {
                udpSocket.SendTo(encoding.GetBytes(logStr), endPoint);
            }
           
            Console.WriteLine(logStr);
            
        }
    }
}